package com.zoe.onelink.logging.trace.util;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;

/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2021</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2021-11-11
 */
public class SqlParserUtil {

    private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";

    public static final ThreadLocal<SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat(DATE_PATTERN));

    /**
     * 带参数的sql语法 , 该拦截器会在{@link com.baomidou.mybatisplus.core.handlers.MetaObjectHandler}之前执行
     * 所以自动设置的更新时间和创建时间等字段会显示为 null
     */
    public static String fillSqlParam(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        StringBuilder sqlStrBuilder = new StringBuilder(boundSql.getSql());
        StringBuilder rawSql = removeSqlComment(sqlStrBuilder);
        if (parameterMappings.size() == 0 || parameterObject == null) {
            return rawSql.toString();
        }
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        String fillValue = "";
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            fillValue = getParameterValue(parameterObject);
            int lastQueMarkIndex = rawSql.indexOf("?");
            rawSql.replace(lastQueMarkIndex, lastQueMarkIndex + 1, Matcher.quoteReplacement(fillValue));
        } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            int lastQueMarkIndex = -1;
            for (ParameterMapping parameterMapping : parameterMappings) {
                String propertyName = parameterMapping.getProperty();
                if (metaObject.hasGetter(propertyName)) {
                    Object obj = metaObject.getValue(propertyName);
                    fillValue = getParameterValue(obj);
                } else if (boundSql.hasAdditionalParameter(propertyName)) {
                    Object obj = boundSql.getAdditionalParameter(propertyName);
                    fillValue = getParameterValue(obj);
                }
                lastQueMarkIndex = rawSql.indexOf("?", lastQueMarkIndex);
                String replacementVal = Matcher.quoteReplacement(fillValue);
                rawSql.replace(lastQueMarkIndex, lastQueMarkIndex + 1, replacementVal);
                lastQueMarkIndex += replacementVal.length();
            }
        }
        return rawSql.toString();
    }

    /**
     * 去掉sql注释和多余的空格以及换行符
     */
    private static StringBuilder removeSqlComment(StringBuilder sqlStrBuilder) {
        int len = sqlStrBuilder.length();
        int lastLineIndex = len;
        char lastChar = '@';
        for (int i = len - 1; i >= 0; i--) {
            char currChar = sqlStrBuilder.charAt(i);
            // 换行符
            if (currChar == '\n') {
                sqlStrBuilder.replace(i, i + 1, " ");
                lastLineIndex = i;
                // 连续两个空格,移除上一个空格
            } else if (currChar == ' ' && lastChar == ' ') {
                sqlStrBuilder.delete(i + 1, i + 2);
                // 双横杆 '--' 方式的注释
            } else if (currChar == '-' && lastChar == '-') {
                sqlStrBuilder.replace(i, lastLineIndex, " ");
                lastLineIndex = i;
            }
            lastChar = currChar;
        }
        return sqlStrBuilder;
    }


    private static String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = String.format("'%s'", obj.toString().replace("'", "`"));
        } else if (obj instanceof Date) {
            String dateTimeStr = DATE_FORMAT_THREAD_LOCAL.get().format((Date) obj);
            value = String.format("to_date('%s','%s')", dateTimeStr, "yyyy-mm-dd hh24:mi:ss");
        } else {
            value = obj == null ? "null" : obj.toString();
        }
        return value;
    }
}
